{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "initial_id",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:39.580119Z",
     "start_time": "2025-01-21T14:09:39.573580Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=10, micro=14, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cu124\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "1dad802b6500ab83",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.656263Z",
     "start_time": "2025-01-21T14:09:39.699901Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(PosixPath('competitions/cifar-10/train/1.png'), 'frog'),\n",
      " (PosixPath('competitions/cifar-10/train/2.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/3.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/4.png'), 'deer'),\n",
      " (PosixPath('competitions/cifar-10/train/5.png'), 'automobile')]\n",
      "[(PosixPath('competitions/cifar-10/test/1.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/2.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/3.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/4.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/5.png'), 'cat')]\n",
      "50000 300000\n"
     ]
    }
   ],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "# 目的：通过读取csv文件，得到图片的类别，并将图片路径和类别保存到DataFrame中。\n",
    "\n",
    "DATA_DIR1 = Path(\"./\")\n",
    "DATA_DIR2=Path(\"./competitions/cifar-10/\")\n",
    "\n",
    "train_labels_file = DATA_DIR1 / \"trainLabels.csv\"\n",
    "test_csv_file = DATA_DIR1 / \"sampleSubmission.csv\"  # 测试集模板csv文件\n",
    "train_folder = DATA_DIR2 / \"train/\"\n",
    "test_folder = DATA_DIR2 / \"test/\"\n",
    "\n",
    "# 所有的类别\n",
    "class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']\n",
    "\n",
    "\n",
    "# filepath:csv文件路径，folder:图片所在文件夹\n",
    "def parse_csv_file(filepath, folder):\n",
    "    result = []\n",
    "    # 读取所有行\n",
    "    # with 语句：用于管理文件的上下文。在 with 块结束时，文件会自动关闭，无需手动调用 f.close()。\n",
    "    with open(filepath, 'r') as f:\n",
    "        # f.readlines()：读取文件的所有行，返回一个列表，每个元素是一行内容。\n",
    "        # 第一行不需要，因为第一行是标题\n",
    "        lines = f.readlines()[1:]\n",
    "    for line in lines:\n",
    "        # strip('\\n')：去除行末的换行符（\\n）。\n",
    "        # split(',')：按','分割字符串，返回一个列表。\n",
    "        image_id, label_str = line.strip('\\n').split(',')\n",
    "        # 得到图片的路径\n",
    "        image_full_path = folder / f\"{image_id}.png\"\n",
    "        # 得到对应图片的路径和分类\n",
    "        result.append((image_full_path, label_str))\n",
    "    return result\n",
    "\n",
    "\n",
    "# 得到训练集和测试集的图片路径和分类\n",
    "train_labels_info = parse_csv_file(train_labels_file, train_folder)\n",
    "test_csv_info = parse_csv_file(test_csv_file, test_folder)\n",
    "\n",
    "#打印\n",
    "import pprint\n",
    "\n",
    "pprint.pprint(train_labels_info[0:5])\n",
    "pprint.pprint(test_csv_info[0:5])\n",
    "print(len(train_labels_info), len(test_csv_info))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "8cd5063c39c678cf",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.732204Z",
     "start_time": "2025-01-21T14:09:41.658275Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                            filepath       class\n",
      "0  competitions/cifar-10/train/1.png        frog\n",
      "1  competitions/cifar-10/train/2.png       truck\n",
      "2  competitions/cifar-10/train/3.png       truck\n",
      "3  competitions/cifar-10/train/4.png        deer\n",
      "4  competitions/cifar-10/train/5.png  automobile\n",
      "                                filepath       class\n",
      "0  competitions/cifar-10/train/45001.png       horse\n",
      "1  competitions/cifar-10/train/45002.png  automobile\n",
      "2  competitions/cifar-10/train/45003.png        deer\n",
      "3  competitions/cifar-10/train/45004.png  automobile\n",
      "4  competitions/cifar-10/train/45005.png    airplane\n",
      "                           filepath class\n",
      "0  competitions/cifar-10/test/1.png   cat\n",
      "1  competitions/cifar-10/test/2.png   cat\n",
      "2  competitions/cifar-10/test/3.png   cat\n",
      "3  competitions/cifar-10/test/4.png   cat\n",
      "4  competitions/cifar-10/test/5.png   cat\n"
     ]
    }
   ],
   "source": [
    "# 取前45000张图片作为训练集\n",
    "train_df = pd.DataFrame(train_labels_info[0:45000])\n",
    "# 取后5000张图片作为验证集\n",
    "valid_df = pd.DataFrame(train_labels_info[45000:])\n",
    "# 测试集\n",
    "test_df = pd.DataFrame(test_csv_info)\n",
    "\n",
    "# 为 Pandas DataFrame 的列重新命名\n",
    "train_df.columns = ['filepath', 'class']\n",
    "valid_df.columns = ['filepath', 'class']\n",
    "test_df.columns = ['filepath', 'class']\n",
    "\n",
    "print(train_df.head())\n",
    "print(valid_df.head())\n",
    "print(test_df.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "af9444393c2debca",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.740688Z",
     "start_time": "2025-01-21T14:09:41.732204Z"
    }
   },
   "outputs": [],
   "source": [
    "from PIL import Image\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "from torchvision import transforms\n",
    "\n",
    "\n",
    "class Cifar10Dataset(Dataset):\n",
    "    df_map = {\n",
    "        \"train\": train_df,\n",
    "        \"eval\": valid_df,\n",
    "        \"test\": test_df\n",
    "    }\n",
    "    # enumerate(class_names)：遍历 class_names，返回索引和类别名称的元组\n",
    "    # 将类别名称映射为索引，通常用于将标签转换为模型可以处理的数值。\n",
    "    label_to_idx = {\n",
    "        label: idx for idx, label in enumerate(class_names)\n",
    "    }\n",
    "    # 将索引映射为类别名称，通常用于将模型的输出（索引）转换回可读的类别名称\n",
    "    idx_to_label = {\n",
    "        idx: label for idx, label in enumerate(class_names)\n",
    "    }\n",
    "\n",
    "    def __init__(self, mode, transform=None):\n",
    "        # 获取对应模式的df，不同字符串对应不同模式\n",
    "        self.df = self.df_map.get(mode, None)\n",
    "        if self.df is None:\n",
    "            raise ValueError(f\"Invalid mode: {mode}\")\n",
    "        self.transform = transform\n",
    "\n",
    "    def __getitem__(self, index):\n",
    "        # 获取图片路径和标签\n",
    "        img_path, label = self.df.iloc[index]\n",
    "        # 打开一张图片并将其转换为 RGB 格式\n",
    "        img = Image.open(img_path).convert('RGB')  # 确保图片具有三个通道\n",
    "        # 应用数据增强\n",
    "        img = self.transform(img)\n",
    "        # label 转换为 idx\n",
    "        label = self.label_to_idx[label]\n",
    "        return img, label\n",
    "\n",
    "    def __len__(self):\n",
    "        # 返回df的行数,样本数\n",
    "        return self.df.shape[0]\n",
    "\n",
    "\n",
    "IMAGE_SIZE = 32\n",
    "mean, std = [0.4914, 0.4822, 0.4465], [0.247, 0.243, 0.261]\n",
    "\n",
    "#数据增强\n",
    "# ToTensor还将图像的维度从[height, width, channels]转换为[channels, height, width]。\n",
    "transforms_train = transforms.Compose([\n",
    "    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),  # 缩放\n",
    "    transforms.RandomRotation(40),  # 随机旋转\n",
    "    transforms.RandomHorizontalFlip(),  # 随机水平翻转\n",
    "    transforms.ToTensor(),  # 转换为Tensor\n",
    "    transforms.Normalize(mean, std)  # 标准化\n",
    "])\n",
    "\n",
    "transforms_eval = transforms.Compose([\n",
    "    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n",
    "    transforms.ToTensor(),\n",
    "    transforms.Normalize(mean, std)\n",
    "])\n",
    "\n",
    "train_ds = Cifar10Dataset(mode=\"train\", transform=transforms_train)\n",
    "eval_ds = Cifar10Dataset(mode=\"eval\", transform=transforms_eval)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "d17ef4a56f0b6ae1",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.748174Z",
     "start_time": "2025-01-21T14:09:41.742701Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([3, 32, 32])"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_ds[0][0].shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "161282a4e612afcd",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.753134Z",
     "start_time": "2025-01-21T14:09:41.749187Z"
    }
   },
   "outputs": [],
   "source": [
    "batch_size = 64\n",
    "\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size,\n",
    "                          shuffle=True)\n",
    "eval_loader = DataLoader(eval_ds, batch_size=batch_size,\n",
    "                         shuffle=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20b5b31416450980",
   "metadata": {},
   "source": [
    "## 模型定义"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "b73daeeb83b4b527",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.760182Z",
     "start_time": "2025-01-21T14:09:41.754138Z"
    }
   },
   "outputs": [],
   "source": [
    "class Resdiual(nn.Module):\n",
    "    \"\"\"\n",
    "    浅层的残差块，无bottleneck（性能限制\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, input_channels, output_channels,\n",
    "                 use_1x1conv=False, stride=1):\n",
    "        \"\"\"\n",
    "        残差块\n",
    "        params filters: 过滤器数目，决定输出通道\n",
    "        params use_1x1conv: 是否使用 1x1 卷积，此时 stride=2，进行降采样\n",
    "        params strides: 步长，默认为1，当降采样的时候设置为2\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        # [1+(n+2-3)]//1=n \n",
    "        # [1+(n+2-3)]//2=n//2\n",
    "        # 即该方法可能会降采样\n",
    "        self.conv1 = nn.Conv2d(\n",
    "            in_channels=input_channels,\n",
    "            out_channels=output_channels,\n",
    "            kernel_size=3,\n",
    "            stride=stride,\n",
    "            padding=1\n",
    "        )\n",
    "        # [1+(n+2-3)]//1=n \n",
    "        self.conv2 = nn.Conv2d(\n",
    "            in_channels=output_channels,\n",
    "            out_channels=output_channels,\n",
    "            kernel_size=3,\n",
    "            stride=1,\n",
    "            padding=1\n",
    "        )\n",
    "        if use_1x1conv:\n",
    "            # skip connection 的 1x1 卷积，用于改变通道数和降采样，使得最终可以做残差连接\n",
    "            # [1+n-1]//s=n//s\n",
    "            self.conv_sc = nn.Conv2d(\n",
    "                in_channels=input_channels,\n",
    "                out_channels=output_channels,\n",
    "                kernel_size=1,\n",
    "                stride=stride\n",
    "            )\n",
    "        else:\n",
    "            self.conv_sc = None\n",
    "\n",
    "        self.bn1 = nn.BatchNorm2d(output_channels, eps=1e-5, momentum=0.9)\n",
    "        self.bn2 = nn.BatchNorm2d(output_channels, eps=1e-5, momentum=0.9)\n",
    "\n",
    "    def forward(self, inputs):\n",
    "        flow = F.relu(self.bn1(self.conv1(inputs)))  # 卷积->BN->ReLU\n",
    "        flow = self.bn2(self.conv2(flow))  # 卷积->BN\n",
    "        if self.conv_sc:\n",
    "            inputs = self.conv_sc(inputs)  # 1x1卷积，改变通道数和降采样\n",
    "        # 残差连接->ReLU，必须保证flow和inputs的shape相同\n",
    "        return F.relu(flow + inputs)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "fc16467b5391fd55",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.766476Z",
     "start_time": "2025-01-21T14:09:41.761185Z"
    }
   },
   "outputs": [],
   "source": [
    "class ResdiualBlock(nn.Module):\n",
    "    \"\"\"\n",
    "    若干个 Resdiual 模块堆叠在一起，\n",
    "    通常在第一个模块给 skip connection 使用 1x1conv with stride=2\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, input_channels, output_channels, num, is_first=False):\n",
    "        \"\"\"\n",
    "        params filters: 过滤器数目\n",
    "        params num: 堆叠几个 Resdiual 模块\n",
    "        params is_first: 是不是第一个block。 最上面一层 Resdiual 的 stride=1,is_first=False,图像尺寸减半，False图像尺寸不变\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        self.model = nn.Sequential()  # 用于存放 Resdiual 模块\n",
    "        # append() 等价于 add_module()\n",
    "        self.model.append(Resdiual(\n",
    "            input_channels=input_channels,\n",
    "            output_channels=output_channels,\n",
    "            use_1x1conv=not is_first,\n",
    "            stride=1 if is_first else 2\n",
    "        ))  # 第一个 Resdiual 模块，负责通道翻倍,图像的尺寸减半\n",
    "        for _ in range(1, num):\n",
    "            # 堆叠 num 个 Resdiual 模块\n",
    "            self.model.append(Resdiual(\n",
    "                input_channels=output_channels,\n",
    "                output_channels=output_channels,\n",
    "                use_1x1conv=False,\n",
    "                stride=1\n",
    "            ))\n",
    "\n",
    "    def forward(self, inputs):\n",
    "        return self.model(inputs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "8128761d5d6c0e1d",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.773688Z",
     "start_time": "2025-01-21T14:09:41.767479Z"
    }
   },
   "outputs": [],
   "source": [
    "class ResNetCifar10(nn.Module):\n",
    "    def __init__(self, n=3, num_classes=10):\n",
    "        \"\"\"\n",
    "        params units: 预测类别的数目\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        self.model = nn.Sequential(\n",
    "            # conv1\n",
    "            nn.Conv2d(in_channels=3, out_channels=16,\n",
    "                      kernel_size=3, stride=1),\n",
    "            nn.BatchNorm2d(16, momentum=0.9, eps=1e-5),\n",
    "            nn.ReLU(),\n",
    "            # conv2_x\n",
    "            ResdiualBlock(input_channels=16, output_channels=16,\n",
    "                          num=2 * n, is_first=True),\n",
    "            # conv3_x\n",
    "            ResdiualBlock(input_channels=16, output_channels=32,\n",
    "                          num=2 * n),\n",
    "            # conv4_x\n",
    "            ResdiualBlock(input_channels=32, output_channels=64,\n",
    "                          num=2 * n),\n",
    "            # 全局平均池化层\n",
    "            #无论输入图片大小，输出都是1x1，把width和height压缩为1\n",
    "            nn.AdaptiveAvgPool2d((1, 1)),\n",
    "            # 全连接层\n",
    "            nn.Flatten(),  # 64*1*1 -> 64\n",
    "            # 输出层\n",
    "            nn.Linear(in_features=64, out_features=num_classes)\n",
    "        )\n",
    "        self.init_weights()\n",
    "\n",
    "    def init_weights(self):\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                # Kaiming 初始化（也称为 He 初始化）是为深度神经网络设计的一种初始化方法，特别适用于 ReLU 激活函数。\n",
    "                nn.init.kaiming_uniform_(m.weight)\n",
    "                if m.bias is not None:\n",
    "                    nn.init.zeros_(m.bias)\n",
    "    \n",
    "    def forward(self, inputs):\n",
    "        return self.model(inputs)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "bb519b4756d62090",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.777720Z",
     "start_time": "2025-01-21T14:09:41.774691Z"
    }
   },
   "outputs": [],
   "source": [
    "# for key, value in ResNetCifar10(len(class_names)).named_parameters():\n",
    "#     print(f\"{key:^40}paramerters num: {np.prod(value.shape)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "b294d25e7f6287d0",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.825513Z",
     "start_time": "2025-01-21T14:09:41.779726Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total trainable parameters: 1929546\n"
     ]
    }
   ],
   "source": [
    "total_params = sum(p.numel() for p in ResNetCifar10(len(class_names)).parameters() if p.requires_grad)\n",
    "print(f\"Total trainable parameters: {total_params}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76ca49d0dca74376",
   "metadata": {},
   "source": [
    "## 模型训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "632d31ce33563b0c",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.909287Z",
     "start_time": "2025-01-21T14:09:41.826518Z"
    }
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "@torch.no_grad()  # 装饰器，禁止梯度计算\n",
    "def evaluate(model, data_loader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in data_loader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "\n",
    "        # 前向传播\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)  # 验证集损失\n",
    "        # tensor.item() 获取tensor的数值，loss是只有一个元素的tensor\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "        # 预测\n",
    "        preds = logits.argmax(axis=-1)  # 预测类别\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())  # tensor转numpy，再转list\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "\n",
    "    acc = accuracy_score(label_list, pred_list)  # 计算准确率\n",
    "    return np.mean(loss_list), acc  # # 返回验证集平均损失和准确率"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "28c2ba49f4ff546",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.915505Z",
     "start_time": "2025-01-21T14:09:41.910290Z"
    }
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=500, save_best_only=True):\n",
    "        self.save_dir = save_dir  # 保存路径\n",
    "        self.save_step = save_step  # 保存步数\n",
    "        self.save_best_only = save_best_only  # 是否只保存最好的模型\n",
    "        self.best_metric = -1  # 最好的指标，指标不可能为负数，所以初始化为-1\n",
    "        # 创建保存路径\n",
    "        if not os.path.exists(self.save_dir):  # 如果不存在保存路径，则创建\n",
    "            os.makedirs(self.save_dir)\n",
    "\n",
    "    # 对象被调用时：当你将对象像函数一样调用时，Python 会自动调用 __call__ 方法。\n",
    "    # state_dict() 返回模型参数的字典，包括模型参数和优化器参数\n",
    "    # metric 是指标，可以是验证集的准确率，也可以是其他指标\n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return  # 不是保存步数，则直接返回\n",
    "\n",
    "        if self.save_best_only:\n",
    "            assert metric is not None  # 必须传入metric\n",
    "            if metric >= self.best_metric:  # 如果当前指标大于最好的指标\n",
    "                # save checkpoint\n",
    "                # 保存最好的模型，覆盖之前的模型，不保存step，只保存state_dict，即模型参数，不保存优化器参数\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"08_resnet.ckpt\"))\n",
    "                self.best_metric = metric  # 更新最好的指标\n",
    "        else:\n",
    "            # 保存模型\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "            # 保存每个step的模型，不覆盖之前的模型，保存step，保存state_dict，即模型参数，不保存优化器参数\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "cd4d61586ad24eb5",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.921575Z",
     "start_time": "2025-01-21T14:09:41.916508Z"
    }
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        self.patience = patience  # 多少个step没有提升就停止训练\n",
    "        self.min_delta = min_delta  # 最小的提升幅度\n",
    "        self.best_metric = -1  # 记录的最好的指标\n",
    "        self.counter = 0  # 计数器，记录连续多少个step没有提升\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:  # 如果指标提升了\n",
    "            self.best_metric = metric  # 更新最好的指标\n",
    "            self.counter = 0  # 计数器清零\n",
    "        else:\n",
    "            self.counter += 1  # 计数器加一\n",
    "\n",
    "    @property  # 使用@property装饰器，使得 对象.early_stop可以调用，不需要()\n",
    "    def early_stop(self):\n",
    "        # 如果计数器大于等于patience，则返回True，停止训练\n",
    "        return self.counter >= self.patience"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "725cf8a521379801",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.930997Z",
     "start_time": "2025-01-21T14:09:41.922578Z"
    }
   },
   "outputs": [],
   "source": [
    "def training(model,\n",
    "             train_loader,\n",
    "             val_loader,\n",
    "             epoch,\n",
    "             loss_fct,\n",
    "             optimizer,\n",
    "             save_ckpt_callback=None,\n",
    "             early_stop_callback=None,\n",
    "             eval_step=500,\n",
    "             ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0  # 全局步数\n",
    "    model.train()  # 训练模式\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "\n",
    "                # 前向传播\n",
    "                logits = model(datas)\n",
    "                loss = loss_fct(logits, labels)  # 训练集损失\n",
    "                preds = logits.argmax(axis=-1)  # 预测类别\n",
    "\n",
    "                # 反向传播\n",
    "                optimizer.zero_grad()  # 梯度清零\n",
    "                loss.backward()  # 反向传播\n",
    "                optimizer.step()  # 优化器更新参数\n",
    "\n",
    "                # 计算准确率\n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())\n",
    "                loss = loss.cpu().item()\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss,\n",
    "                    \"acc\": acc,\n",
    "                    \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # 评估\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()  # 评估模式\n",
    "                    # 验证集损失和准确率\n",
    "                    val_loss, val_acc = evaluate(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss,\n",
    "                        \"acc\": val_acc,\n",
    "                        \"step\": global_step\n",
    "                    })\n",
    "                    model.train()  # 训练模式\n",
    "\n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        # model.state_dict() 返回模型参数的字典，包括模型参数和优化器参数\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), val_acc)\n",
    "                        # 保存最好的模型，覆盖之前的模型，保存step，保存state_dict,通过metric判断是否保存最好的模型\n",
    "\n",
    "                    # 3. 早停 early stopping\n",
    "                    if early_stop_callback is not None:\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        early_stop_callback(val_acc)\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict  # 早停，返回记录字典 record_dict\n",
    "\n",
    "                # 更新进度条和全局步数\n",
    "                pbar.update(1)  # 更新进度条\n",
    "                global_step += 1  # 全局步数加一\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict  # 训练结束，返回记录字典 record_dict\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "22bf1e82c9715088",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:09:41.979444Z",
     "start_time": "2025-01-21T14:09:41.931999Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "epoch = 100\n",
    "\n",
    "model = ResNetCifar10(len(class_names))  # 定义模型\n",
    "model = model.to(device)  # 将模型移到GPU上\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "\n",
    "# 2. 定义优化器 采用 Adagrad\n",
    "optimizer = torch.optim.Adagrad(model.parameters(), lr=0.001)\n",
    "\n",
    "# 3.save model checkpoint\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(save_dir=\"checkpoints\", save_step=len(train_loader), save_best_only=True)\n",
    "\n",
    "# 4. early stopping\n",
    "early_stop_callback = EarlyStopCallback(patience=5, min_delta=0.01)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "5335f62fe5e4977f",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:10:19.118493Z",
     "start_time": "2025-01-21T14:09:41.980446Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 25%|██▌       | 17600/70400 [21:20<1:04:01, 13.74it/s, epoch=24]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 25 / global_step 17600\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "\n",
    "# 训练过程\n",
    "record_dict = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    eval_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "1a790e5f4635d5e8",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:10:19.120498Z",
     "start_time": "2025-01-21T14:10:19.119497Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAzYAAAHECAYAAADrir96AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAuqNJREFUeJzs3Xd4W/XVwPGvtveK4xFnD7InGYQZIIOkUAKlpWxCS99SArRpKaQtgdCWsAphFSizlNmWWQghJhBGCJkEkpC9l+04HvLSvu8fV1e2Y9mWbC1L5/M8eWJf6UpX11Zyj875naNTFEVBCCGEEEIIIbowfbQPQAghhBBCCCE6SwIbIYQQQgghRJcngY0QQgghhBCiy5PARgghhBBCCNHlSWAjhBBCCCGE6PIksBFCCCGEEEJ0eRLYCCGEEEIIIbo8CWyEEEIIIYQQXZ4ENkIIIYQQQoguTwIbIYQQQgghRJcngY0QQogu54knnqBv374kJSUxadIk1qxZ0+p9p0yZgk6na/HnBz/4QQSPWAghRLhJYCOEEKJLeeONN5g3bx533nknGzZsYPTo0cyYMYOysjK/93/rrbc4evSo78/mzZsxGAz8+Mc/jvCRCyGECCedoihKtA+iKY/Hw5EjR0hPT0en00X7cIQQIqEoikJNTQ09evRAr4/Nz74mTZrEhAkTePzxxwH1/41evXpx0003cfvtt7e7/+LFi1mwYAFHjx4lNTU1oOeU/5uEECI6gvl/yRihYwrYkSNH6NWrV7QPQwghEtrBgwfp2bNntA+jBYfDwfr165k/f75vm16vZ+rUqaxatSqgx3juuef46U9/2mZQY7fbsdvtvu8PHz7MsGHDOn7gQgghOiWQ/5diLrBJT08H1IPPyMgIen+n08myZcuYPn06JpMp1IcnmpBzHTlyriMn0c+11WqlV69evn+LY015eTlut5v8/Pxm2/Pz89m2bVu7+69Zs4bNmzfz3HPPtXm/RYsWsXDhwhbbn332WVJSUoI7aCGEEB1WX1/Pz3/+84D+X4q5wEZL8WdkZHQ4sElJSSEjIyMhL0oiSc515Mi5jhw516p4Lbd67rnnGDlyJBMnTmzzfvPnz2fevHm+77WAb/bs2R3+v6m4uJhp06Yl9O9VJMi5jhw515GTyOfaarXy85//PKD/l2IusBFCCCFak5ubi8FgoLS0tNn20tJSCgoK2ty3rq6O119/nbvvvrvd57FYLFgslhbbTSZTpy4qOru/CJyc68iRcx05iXiug3m9sbkyVAghhPDDbDZz8skns3z5ct82j8fD8uXLmTx5cpv7/uc//8Fut3PllVeG+zCFEEJEgWRshBBCdCnz5s3jmmuuYfz48UycOJHFixdTV1fHnDlzALj66qspKipi0aJFzfZ77rnnmD17Nt26dYvGYQshhAizoAObzz//nAceeID169dz9OhR3n77bWbPnt3sPlu3buW2227js88+w+VyMWzYMN5880169+4dquMWQkSRoii4XC7cbne0DyXknE4nRqMRm80Wl6/PYDBgNBq79BqaSy+9lGPHjrFgwQJKSkoYM2YMS5cu9TUUOHDgQIuWoNu3b+fLL79k2bJlYTuutt4X8f57FUsCPdfx8F4QQjQXdGBTV1fH6NGjue6667j44otb3L57925OP/10fvazn7Fw4UIyMjLYsmULSUlJITlgIUR0ORwOjh49Sn19fbQPJSwURaGgoICDBw/G7QVPSkoKhYWFmM3maB9Kh82dO5e5c+f6vW3FihUttg0ePJhwjm1r732RCL9XsSKYcx0P7wUhRKOgA5uZM2cyc+bMVm//4x//yKxZs7j//vt92wYMGNDq/U+cFWC1WgH1Exen0xns4fn26ci+IjhyriMnVs61x+Nh7969GAwGCgsLMZlMcXeRpigKdXV1pKamxuVrczqdHDt2jD179tCvX78WmY1o/451RU3fFz169MBsNrf43fF4PNTW1pKWlhazg0/jRSDnWlEUHA4Hx44dY+/evQwaNEh+LkLEgZCusfF4PHzwwQf8/ve/Z8aMGXzzzTf069eP+fPntyhX07Q2K2DZsmWdmhVQXFzc4X1FcORcR060z7XRaKSgoMA3ICteL4LNZnPcvjZQ2+kfOnSI4uLiFqU68ZqJCyeHw4HH46FXr16t/r/l8XhwOBwkJSXJBXSYBXquk5OTMZlM7N+/33d/IUTXFtLApqysjNraWu69917+8pe/cN9997F06VIuvvhiPv30U84666wW+7Q2K2D69OkyKyDGybmOnFg51zabjYMHD5Kenh63FwGKolBTU0N6enrcZWw0NpuN5ORkzjrrrBY/Ry1rLoInAUvXIz8zIeJLyDM2ABdeeCG/+c1vABgzZgxfffUVTz31lN/ARmYFdH1yriMn2ufa7Xaj0+nQ6/Vxe0Gg/Tumvc54pNfr0el0fn+f5L0shBCiqwrp/9q5ubkYjUaGDRvWbPvQoUM5cOBAKJ9KCCGEEEIIIXxCGtiYzWYmTJjA9u3bm23fsWMHffr0CeVTCSFE1PTt25fFixd36jGuvfbaVtceCtEVheJ9IYQQnRF0KVptbS27du3yfb937142btxITk4OvXv35tZbb+XSSy/lzDPP5Oyzz2bp0qX873//89t+UwghImXKlCmMGTMmJBdea9euJTU1tfMHJUSUyftCCBFPgg5s1q1bx9lnn+37Xlv4f8011/Diiy9y0UUX8dRTT7Fo0SJuvvlmBg8ezJtvvsnpp58euqMWQogQUxQFt9sd0Lqa7t27R+CIhIg+7X1hNLZ/uSDvCyFEtAVdijZlyhQURWnx58UXX/Td57rrrmPnzp00NDSwceNGLrzwwlAec5v0Xz3K2Vv/gH7tPyL2nEIkMkVRqHe4Iv4nmGGL1157LZ999hmPPPIIOp0OnU7Hiy++iE6n48MPP+Tkk0/GYrHw5Zdfsnv3bi6//HIKCwtJS0tjwoQJfPzxx80e78SSG51Ox7PPPstFF11ESkoKgwYN4r333gvqPNrtdm6++Wby8vJISkri9NNPZ+3atb7bKysrueKKK+jevTvJyckMGjSIF154AVDbDc+dO5fCwkKSkpLo06cPixYtCur5RWj5e180ONxd+n1x4YUXkp+fH9b3xdq1a5k2bRq5ublkZmZy1llnsWHDhmb3qaqq4v/+7//Iz88nKSmJESNG8P777/tuX7lyJeeffz5paWlkZ2czY8YMKisrAz4vQkTa35ZtZ/5bm8I6RDhRhLQrWkyoP0aG7RDumqPRPhIhEkKD082wBR9F/Hm/v3sGKebA/gl75JFH2LFjByNGjODuu+8GYMuWLQDcfvvtPPjgg/Tv35/s7Gz279/PtGnTuPfee0lOTuall17iggsuYPv27fTu3bvV51i4cCH3338/DzzwAI899hhXXHEF+/fvJycnB1Av+q699lruuusuv/v//ve/58033+Sf//wnffr04f7772fGjBns2rWLnJwc7rjjDr7//ns+/PBDcnNz2bVrFw0NDQA8+uijvPfee/z73/+md+/eHDx4kIMHDwZ6KkUYxNv74uDBg8yaNYu//vWvWCyWsL0vampquOaaa3jsscdQFIW//e1vzJo1i507d5Keno7H42HmzJnU1NTw8ssvM2DAAL7//nsMBgMAGzduZNq0aVxxxRU89thjmM1mPv300xbzmoSIFQ6Xh8c/3YWiwM3nDqQwMznah9SlxV9gY/QOR3M2RPc4hBAxIzMzE7PZTEpKCgUFBQBs27YNgLvvvptp06b57puVlUW/fv3IyMhAr9fz5z//mbfffpv33nuPuXPntvoc1157LZdddhkA99xzD48++ihr1qzhvPPOA2DAgAHk5ub63beuro4nn3ySF198kZkzZwLwzDPPUFxczHPPPcett97KgQMHGDt2LOPHjwfUC0LNgQMHGDRoEKeffjo6nU6atYiABPO+yMnJYfTo0b7vw/W+OOecc5rt/49//IOsrCw+++wzzj//fD7++GPWrFnD1q1bOemkkwDo37+/7/73338/48eP529/+5vvPTx8+PAOnR8hIqGy3oGWqLE2uCjMjO7xdHXxF9iY1EhXJ4GNEBGRbDLw/d0zovK8oaAFCpra2lruuOMOPv74Y44ePYrL5aKhoaHdlvWjRo3yfZ2amkpGRgZlZWW+bcuXL2913927d+N0OjnttNN820wmExMnTmTr1q0A3HDDDfzoRz9iw4YNTJ8+ndmzZ3PqqacC6sXjtGnTGDx4MOeddx7nn38+06dPD/wkiJA78X3h8XiosdaQnpEe1vlI4Xxf3HXXXXzwwQdhfV+Ulpbypz/9iRUrVlBWVobb7aa+vt73PBs3bqRnz56+oOZEGzdu5JJLLgnqtQoRTcdrHb6va2zOKB5JfIjDwMY7RdslgY0QkaDT6QIufYlFJ3ZxuvXWW1m2bBkPPvggJ510EsnJyVxyySU4HI5WHkF14mBLnU7nG/YZCjNnzmT//v0sWbKE4uJizj33XG688UYefPBBxo0bx969e/nwww/5+OOP+clPfsLUqVP573//G7LnF8E58X3h8XhwmQ2kmI1dYvDrie+L3/3udxQXF/Pggw8ycODAsL0vrrnmGo4fP84jjzxCnz59sFgsTJ482fc8ycltl+m0d7sQsaairmlg44rikcSH2P/XNUiK0fuPmmRshBBNmM3mgOrsv/rqKy6//HIuuugiRo4cSUFBAfv27QvrsQ0YMACz2czKlSt925xOJ2vXrm028Lh79+5cc801vPzyyyxevJh//KOxSUpGRgaXXnop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",
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def plot_record_curves(record_dict, sample_step=500):\n",
    "    # .set_index(\"step\") 将 step 列设置为 DataFrame 的索引\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    last_step = train_df.index[-1]  # 最后一步的步数\n",
    "\n",
    "    # print(train_df)\n",
    "    # print(val_df)\n",
    "\n",
    "    # 画图 \n",
    "    fig_num = len(train_df.columns)  # 画两张图,分别是损失和准确率\n",
    "\n",
    "    # plt.subplots：用于创建一个包含多个子图的图形窗口。\n",
    "    # 1：表示子图的行数为 1。\n",
    "    # fig_num：表示子图的列数，即子图的数量。\n",
    "    # figsize=(5 * fig_num, 5)：设置整个图形窗口的大小，宽度为 5 * fig_num，高度为 5。\n",
    "    # fig：返回的图形对象（Figure），用于操作整个图形窗口。\n",
    "    # axs：返回的子图对象（Axes 或 Axes 数组），用于操作每个子图。\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        # train_df.index 是 x 轴数据（通常是 step）。\n",
    "        # train_df[item] 是 y 轴数据（当前指标的值）。\n",
    "        axs[idx].plot(train_df.index, train_df[item], label=\"train:\" + item)\n",
    "        # val_df.index 是 x 轴数据。\n",
    "        # val_df[item] 是 y 轴数据。\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=\"val:\" + item)\n",
    "        axs[idx].grid()  # 显示网格\n",
    "        axs[idx].legend()  # 显示图例\n",
    "        axs[idx].set_xticks(range(0, train_df.index[-1] + 1, 5000))  # 设置x轴刻度\n",
    "        axs[idx].set_xticklabels(map(lambda x: f\"{x // 1000}k\", range(0, last_step + 1, 5000)))  # 设置x轴标签\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "plot_record_curves(record_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d9729d15a0d01e8",
   "metadata": {},
   "source": [
    "## 评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "3195e10a60c29806",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T14:10:19.121498Z",
     "start_time": "2025-01-21T14:10:19.121498Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test loss: 1.2472, Test acc: 0.5530\n"
     ]
    }
   ],
   "source": [
    "# 加载最好的模型\n",
    "# torch.load：加载保存的模型权重或整个模型。\n",
    "# \"checkpoints/best.ckpt\"：模型权重文件路径。\n",
    "# weights_only=True：仅加载模型的权重，而不是整个模型（包括结构和参数）。这是 PyTorch 2.1 引入的新特性，用于增强安全性。\n",
    "# map_location=device：将模型加载到当前设备（GPU或CPU）。\n",
    "model.load_state_dict(torch.load(\"checkpoints/08_resnet.ckpt\", weights_only=True, map_location=device))  # 加载最好的模型\n",
    "\n",
    "model.eval()  # 评估模式\n",
    "loss, acc = evaluate(model, eval_loader, loss_fct)\n",
    "print(f\"Test loss: {loss:.4f}, Test acc: {acc:.4f}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
